Skip to main content
Skip to main content

Runtime API

The Runtime API is the lowest-level programmatic interface to web3agent. It gives you a Web3AgentRuntime instance that can invoke any registered tool by name, list all available tools, report health status, and shut down cleanly.

Most projects don't need the Runtime API directly. The root SDK ("web3agent") provides typed functions like getSwapQuote() and prepareSwapIntent() that handle runtime management internally. The Runtime API is for cases where you need direct tool invocation, want to manage multiple isolated runtimes, or are building a platform that passes the runtime through as a dependency.


When to Use the Runtime API

Use the root SDK functions when you're calling specific, well-known operations. They're typed, documented, and handle error extraction for you.

Use the Runtime API directly when:

  • You need to invoke tools generically (e.g., a platform that dispatches arbitrary tool calls)
  • You're creating per-agent runtimes with different wallets, chains, or policy limits
  • You want to inspect the tool catalog at runtime
  • You need to pass a runtime instance through your own dependency injection system

Basic Usage

import { createRuntime } from "web3agent/runtime";

const runtime = await createRuntime();

// Check that all integrations initialized correctly
console.log(runtime.getHealth());

// Browse available tools
const tools = runtime.listTools();
console.log(tools.slice(0, 5).map(t => t.name));
// ["list_supported_chains", "server_status", "resolve_token", "orbs_get_quote", ...]

// Invoke a tool by name
const result = await runtime.invokeTool("list_supported_chains");
console.log(result.structuredContent);

// Always shut down when done to release connections
await runtime.shutdown();

Custom Configuration

Pass an env object to createRuntime() to override environment variables for that specific runtime instance. This is how trading-arena creates per-agent runtimes with isolated wallets and policy limits.

import { createRuntime } from "web3agent/runtime";

const runtime = await createRuntime({
  env: {
    CHAIN_ID: "8453",
    PRIVATE_KEY: "0x...",
    CONFIRM_WRITES: "false",
    POLICY_ENABLED: "true",
    POLICY_MAX_SINGLE_TX_USD: "100",
    POLICY_MAX_DAILY_USD: "1000",
  }
});

All values in env are strings, matching how environment variables work. The runtime parses them internally using the same parseEnv() logic as the MCP server.

The env object is merged with the process environment. Keys in env take precedence over process.env. This means you can override specific variables while inheriting the rest (like ETHERSCAN_API_KEY or LIFI_API_KEY) from the process environment.


The Web3AgentRuntime Interface

interface Web3AgentRuntime {
  /**
   * Invoke a registered tool by name with optional arguments.
   * Returns a CallToolResult — check `ok` before using `data`.
   */
  invokeTool(name: string, args?: Record<string, unknown>): Promise<CallToolResult>;

  /**
   * List all registered tools with their names, descriptions, and input schemas.
   */
  listTools(): ToolCatalogEntry[];

  /**
   * Get the health status of all integrations.
   * Returns an object with per-integration status and any initialization errors.
   */
  getHealth(): RuntimeHealth;

  /**
   * Shut down the runtime, releasing connections and clearing state.
   * Call this when the runtime is no longer needed.
   */
  shutdown(): Promise<void>;
}

invokeTool

invokeTool is the core method. It takes a tool name and an optional arguments object, validates the arguments against the tool's Zod schema, and executes the handler.

// Safe read — no wallet needed
const chainsResult = await runtime.invokeTool("list_supported_chains");
if (chainsResult.ok) {
  console.log(chainsResult.data);
}

// Token resolution
const tokenResult = await runtime.invokeTool("resolve_token", {
  symbol: "USDC",
  chainId: 8453,
});
if (tokenResult.ok) {
  console.log(tokenResult.data.address); // "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
}

// Price lookup
const priceResult = await runtime.invokeTool("get_token_price", {
  tokens: ["8453:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"],
});
if (priceResult.ok) {
  console.log(priceResult.data.prices);
}

Always check result.ok before accessing result.data. When ok is false, result.error contains a { code, message } object.

const result = await runtime.invokeTool("orbs_swap", { ... });

if (!result.ok) {
  console.error(`[${result.error.code}] ${result.error.message}`);
  return;
}

console.log(result.data.txHash);

listTools

listTools() returns the full tool catalog. Each entry includes the tool's name, description, input schema (JSON Schema), and risk level.

const tools = runtime.listTools();

// Find all financial-risk tools
const financialTools = tools.filter(t => t.riskLevel === "financial");
console.log(financialTools.map(t => t.name));

// Find tools by prefix
const orbsTools = tools.filter(t => t.name.startsWith("orbs_"));
console.log(`${orbsTools.length} Orbs tools available`);

getHealth

getHealth() returns the initialization status of each integration. Useful for startup checks and debugging.

const health = runtime.getHealth();
console.log(health);
// {
//   status: "healthy",
//   integrations: {
//     orbs: { status: "ok" },
//     lifi: { status: "ok" },
//     evm: { status: "ok" },
//     wallet: { status: "ok", address: "0x..." },
//   }
// }

If an integration fails to initialize (e.g., missing API key, bad RPC URL), its status will be "error" with a message. The runtime still starts, but tools from that integration will return errors when called.


Multiple Runtimes

You can create multiple runtime instances in the same process. Each is fully isolated: separate wallet state, separate policy spend tracking, separate tool invocation history.

import { createRuntime } from "web3agent/runtime";

// Create one runtime per trading agent
const agentRuntimes = await Promise.all(
  agents.map(agent =>
    createRuntime({
      env: {
        CHAIN_ID: "8453",
        PRIVATE_KEY: decrypt(agent.walletPrivateKey, encryptionKey),
        CONFIRM_WRITES: "false",
        POLICY_ENABLED: "true",
        POLICY_MAX_SINGLE_TX_USD: String(agent.maxSingleTxUsd),
        POLICY_MAX_DAILY_USD: String(agent.maxDailyUsd),
      }
    })
  )
);

// Each runtime has its own wallet and policy state
for (const [i, runtime] of agentRuntimes.entries()) {
  const health = runtime.getHealth();
  console.log(`Agent ${i}: ${health.status}`);
}

// Shut them all down when done
await Promise.all(agentRuntimes.map(r => r.shutdown()));

The Default Runtime

The root SDK functions (getSwapQuote, prepareSwapIntent, etc.) don't require you to create a runtime. They call getRuntime() internally, which lazily creates and caches a default runtime singleton using the process environment.

import { getSwapQuote } from "web3agent";

// No runtime setup needed — getRuntime() handles it
const quote = await getSwapQuote({
  fromToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  toToken: "0x4200000000000000000000000000000000000006",
  fromAmount: "100000000",
  chainId: 8453,
});

The default runtime is created once and reused across all SDK calls. To release it (e.g., in tests or short-lived scripts), call shutdownDefaultRuntime():

import { shutdownDefaultRuntime } from "web3agent";

// At the end of your script or test teardown
await shutdownDefaultRuntime();

Injecting a Custom Runtime into SDK Functions

Every SDK function accepts an optional options parameter with a runtime field. Pass a custom runtime to override the default for that call.

import { getSwapQuote } from "web3agent";
import { createRuntime } from "web3agent/runtime";

const customRuntime = await createRuntime({
  env: { CHAIN_ID: "42161" } // Arbitrum
});

const quote = await getSwapQuote(
  {
    fromToken: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // USDC on Arbitrum
    toToken: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",  // WETH on Arbitrum
    fromAmount: "100000000",
    chainId: 42161,
  },
  { runtime: customRuntime } // use this runtime instead of the default
);

await customRuntime.shutdown();

Error Handling

invokeTool never throws. It always returns a CallToolResult. Errors from validation, network failures, or on-chain reverts all come back as { ok: false, error: { code, message } }.

The SDK layer's invokeAndRequireData() converts this into a thrown Web3AgentError for convenience. If you're using invokeTool directly, handle errors explicitly:

import type { Web3AgentError } from "web3agent";

const result = await runtime.invokeTool("orbs_swap", swapArgs);

if (!result.ok) {
  // result.error.code is a string like "ORBS_SWAP_FAILED"
  // result.error.message is a human-readable description
  throw new Error(`Swap failed: [${result.error.code}] ${result.error.message}`);
}

return result.data;

TypeScript Types

The key types from "web3agent/runtime":

import type {
  Web3AgentRuntime,   // The runtime interface
  ToolCatalogEntry,   // A single tool's metadata
  RuntimeHealth,      // Health check result
} from "web3agent/runtime";

import type {
  CallToolResult,     // { ok: true, data: T } | { ok: false, error: { code, message } }
} from "web3agent";